/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management;

import com.sun.jmx.mbeanserver.GetPropertyAction;
import com.sun.jmx.mbeanserver.Util;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
 * <p>Represents the object name of an MBean, or a pattern that can
 * match the names of several MBeans.  Instances of this class are
 * immutable.</p>
 *
 * <p>An instance of this class can be used to represent:</p>
 * <ul>
 * <li>An object name</li>
 * <li>An object name pattern, within the context of a query</li>
 * </ul>
 *
 * <p>An object name consists of two parts, the domain and the key
 * properties.</p>
 *
 * <p>The <em>domain</em> is a string of characters not including
 * the character colon (<code>:</code>).  It is recommended that the domain
 * should not contain the string "{@code //}", which is reserved for future use.
 *
 * <p>If the domain includes at least one occurrence of the wildcard
 * characters asterisk (<code>*</code>) or question mark
 * (<code>?</code>), then the object name is a pattern.  The asterisk
 * matches any sequence of zero or more characters, while the question
 * mark matches any single character.</p>
 *
 * <p>If the domain is empty, it will be replaced in certain contexts
 * by the <em>default domain</em> of the MBean server in which the
 * ObjectName is used.</p>
 *
 * <p>The <em>key properties</em> are an unordered set of keys and
 * associated values.</p>
 *
 * <p>Each <em>key</em> is a nonempty string of characters which may
 * not contain any of the characters comma (<code>,</code>), equals
 * (<code>=</code>), colon, asterisk, or question mark.  The same key
 * may not occur twice in a given ObjectName.</p>
 *
 * <p>Each <em>value</em> associated with a key is a string of
 * characters that is either unquoted or quoted.</p>
 *
 * <p>An <em>unquoted value</em> is a possibly empty string of
 * characters which may not contain any of the characters comma,
 * equals, colon, or quote.</p>
 *
 * <p>If the <em>unquoted value</em> contains at least one occurrence
 * of the wildcard characters asterisk or question mark, then the object
 * name is a <em>property value pattern</em>. The asterisk matches any
 * sequence of zero or more characters, while the question mark matches
 * any single character.</p>
 *
 * <p>A <em>quoted value</em> consists of a quote (<code>"</code>),
 * followed by a possibly empty string of characters, followed by
 * another quote.  Within the string of characters, the backslash
 * (<code>\</code>) has a special meaning.  It must be followed by
 * one of the following characters:</p>
 *
 * <ul>
 * <li>Another backslash.  The second backslash has no special
 * meaning and the two characters represent a single backslash.</li>
 *
 * <li>The character 'n'.  The two characters represent a newline
 * ('\n' in Java).</li>
 *
 * <li>A quote.  The two characters represent a quote, and that quote
 * is not considered to terminate the quoted value. An ending closing
 * quote must be present for the quoted value to be valid.</li>
 *
 * <li>A question mark (?) or asterisk (*).  The two characters represent
 * a question mark or asterisk respectively.</li>
 * </ul>
 *
 * <p>A quote may not appear inside a quoted value except immediately
 * after an odd number of consecutive backslashes.</p>
 *
 * <p>The quotes surrounding a quoted value, and any backslashes
 * within that value, are considered to be part of the value.</p>
 *
 * <p>If the <em>quoted value</em> contains at least one occurrence of
 * the characters asterisk or question mark and they are not preceded
 * by a backslash, then they are considered as wildcard characters and
 * the object name is a <em>property value pattern</em>. The asterisk
 * matches any sequence of zero or more characters, while the question
 * mark matches any single character.</p>
 *
 * <p>An ObjectName may be a <em>property list pattern</em>. In this
 * case it may have zero or more keys and associated values. It matches
 * a nonpattern ObjectName whose domain matches and that contains the
 * same keys and associated values, as well as possibly other keys and
 * values.</p>
 *
 * <p>An ObjectName is a <em>property value pattern</em> when at least
 * one of its <em>quoted</em> or <em>unquoted</em> key property values
 * contains the wildcard characters asterisk or question mark as described
 * above. In this case it has one or more keys and associated values, with
 * at least one of the values containing wildcard characters. It matches a
 * nonpattern ObjectName whose domain matches and that contains the same
 * keys whose values match; if the property value pattern is also a
 * property list pattern then the nonpattern ObjectName can contain
 * other keys and values.</p>
 *
 * <p>An ObjectName is a <em>property pattern</em> if it is either a
 * <em>property list pattern</em> or a <em>property value pattern</em>
 * or both.</p>
 *
 * <p>An ObjectName is a pattern if its domain contains a wildcard or
 * if the ObjectName is a property pattern.</p>
 *
 * <p>If an ObjectName is not a pattern, it must contain at least one
 * key with its associated value.</p>
 *
 * <p>Examples of ObjectName patterns are:</p>
 *
 * <ul>
 * <li>{@code *:type=Foo,name=Bar} to match names in any domain whose
 * exact set of keys is {@code type=Foo,name=Bar}.</li>
 * <li>{@code d:type=Foo,name=Bar,*} to match names in the domain
 * {@code d} that have the keys {@code type=Foo,name=Bar} plus
 * zero or more other keys.</li>
 * <li>{@code *:type=Foo,name=Bar,*} to match names in any domain
 * that has the keys {@code type=Foo,name=Bar} plus zero or
 * more other keys.</li>
 * <li>{@code d:type=F?o,name=Bar} will match e.g.
 * {@code d:type=Foo,name=Bar} and {@code d:type=Fro,name=Bar}.</li>
 * <li>{@code d:type=F*o,name=Bar} will match e.g.
 * {@code d:type=Fo,name=Bar} and {@code d:type=Frodo,name=Bar}.</li>
 * <li>{@code d:type=Foo,name="B*"} will match e.g.
 * {@code d:type=Foo,name="Bling"}. Wildcards are recognized even
 * inside quotes, and like other special characters can be escaped
 * with {@code \}.</li>
 * </ul>
 *
 * <p>An ObjectName can be written as a String with the following
 * elements in order:</p>
 *
 * <ul>
 * <li>The domain.
 * <li>A colon (<code>:</code>).
 * <li>A key property list as defined below.
 * </ul>
 *
 * <p>A key property list written as a String is a comma-separated
 * list of elements.  Each element is either an asterisk or a key
 * property.  A key property consists of a key, an equals
 * (<code>=</code>), and the associated value.</p>
 *
 * <p>At most one element of a key property list may be an asterisk.
 * If the key property list contains an asterisk element, the
 * ObjectName is a property list pattern.</p>
 *
 * <p>Spaces have no special significance in a String representing an
 * ObjectName.  For example, the String:
 * <pre>
 * domain: key1 = value1 , key2 = value2
 * </pre>
 * represents an ObjectName with two keys.  The name of each key
 * contains six characters, of which the first and last are spaces.
 * The value associated with the key <code>"&nbsp;key1&nbsp;"</code>
 * also begins and ends with a space.
 *
 * <p>In addition to the restrictions on characters spelt out above,
 * no part of an ObjectName may contain a newline character
 * (<code>'\n'</code>), whether the domain, a key, or a value, whether
 * quoted or unquoted.  The newline character can be represented in a
 * quoted value with the sequence <code>\n</code>.
 *
 * <p>The rules on special characters and quoting apply regardless of
 * which constructor is used to make an ObjectName.</p>
 *
 * <p>To avoid collisions between MBeans supplied by different
 * vendors, a useful convention is to begin the domain name with the
 * reverse DNS name of the organization that specifies the MBeans,
 * followed by a period and a string whose interpretation is
 * determined by that organization.  For example, MBeans specified by
 * <code>example.com</code>  would have
 * domains such as <code>com.example.MyDomain</code>.  This is essentially
 * the same convention as for Java-language package names.</p>
 *
 * <p>The <b>serialVersionUID</b> of this class is <code>1081892073854801359L</code>.
 *
 * @since 1.5
 */
@SuppressWarnings("serial") // don't complain serialVersionUID not constant
public class ObjectName implements Comparable<ObjectName>, QueryExp {

  /**
   * A structure recording property structure and
   * proposing minimal services
   */
  private static class Property {

    int _key_index;
    int _key_length;
    int _value_length;

    /**
     * Constructor.
     */
    Property(int key_index, int key_length, int value_length) {
      _key_index = key_index;
      _key_length = key_length;
      _value_length = value_length;
    }

    /**
     * Assigns the key index of property
     */
    void setKeyIndex(int key_index) {
      _key_index = key_index;
    }

    /**
     * Returns a key string for receiver key
     */
    String getKeyString(String name) {
      return name.substring(_key_index, _key_index + _key_length);
    }

    /**
     * Returns a value string for receiver key
     */
    String getValueString(String name) {
      int in_begin = _key_index + _key_length + 1;
      int out_end = in_begin + _value_length;
      return name.substring(in_begin, out_end);
    }
  }

  /**
   * Marker class for value pattern property.
   */
  private static class PatternProperty extends Property {

    /**
     * Constructor.
     */
    PatternProperty(int key_index, int key_length, int value_length) {
      super(key_index, key_length, value_length);
    }
  }

  // Inner classes <========================================

  // Private fields ---------------------------------------->

  // Serialization compatibility stuff -------------------->

  // Two serial forms are supported in this class. The selected form depends
  // on system property "jmx.serial.form":
  //  - "1.0" for JMX 1.0
  //  - any other value for JMX 1.1 and higher
  //
  // Serial version for old serial form
  private static final long oldSerialVersionUID = -5467795090068647408L;
  //
  // Serial version for new serial form
  private static final long newSerialVersionUID = 1081892073854801359L;
  //
  // Serializable fields in old serial form
  private static final ObjectStreamField[] oldSerialPersistentFields =
      {
          new ObjectStreamField("domain", String.class),
          new ObjectStreamField("propertyList", Hashtable.class),
          new ObjectStreamField("propertyListString", String.class),
          new ObjectStreamField("canonicalName", String.class),
          new ObjectStreamField("pattern", Boolean.TYPE),
          new ObjectStreamField("propertyPattern", Boolean.TYPE)
      };
  //
  // Serializable fields in new serial form
  private static final ObjectStreamField[] newSerialPersistentFields = {};
  //
  // Actual serial version and serial form
  private static final long serialVersionUID;
  private static final ObjectStreamField[] serialPersistentFields;
  private static boolean compat = false;

  static {
    try {
      GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
      String form = AccessController.doPrivileged(act);
      compat = (form != null && form.equals("1.0"));
    } catch (Exception e) {
      // OK: exception means no compat with 1.0, too bad
    }
    if (compat) {
      serialPersistentFields = oldSerialPersistentFields;
      serialVersionUID = oldSerialVersionUID;
    } else {
      serialPersistentFields = newSerialPersistentFields;
      serialVersionUID = newSerialVersionUID;
    }
  }

  //
  // Serialization compatibility stuff <==============================

  // Class private fields ----------------------------------->

  /**
   * a shared empty array for empty property lists
   */
  static final private Property[] _Empty_property_array = new Property[0];

  // Class private fields <==============================

  // Instance private fields ----------------------------------->

  /**
   * a String containing the canonical name
   */
  private transient String _canonicalName;


  /**
   * An array of properties in the same seq order as time creation
   */
  private transient Property[] _kp_array;

  /**
   * An array of properties in the same seq order as canonical order
   */
  private transient Property[] _ca_array;


  /**
   * The length of the domain part of built objectname
   */
  private transient int _domain_length = 0;


  /**
   * The propertyList of built object name. Initialized lazily.
   * Table that contains all the pairs (key,value) for this ObjectName.
   */
  private transient Map<String, String> _propertyList;

  /**
   * boolean that declares if this ObjectName domain part is a pattern
   */
  private transient boolean _domain_pattern = false;

  /**
   * boolean that declares if this ObjectName contains a pattern on the
   * key property list
   */
  private transient boolean _property_list_pattern = false;

  /**
   * boolean that declares if this ObjectName contains a pattern on the
   * value of at least one key property
   */
  private transient boolean _property_value_pattern = false;

  // Instance private fields <=======================================

  // Private fields <========================================

  //  Private methods ---------------------------------------->

  // Category : Instance construction ------------------------->

  /**
   * Initializes this {@link ObjectName} from the given string
   * representation.
   *
   * @param name A string representation of the {@link ObjectName}
   * @throws MalformedObjectNameException The string passed as a parameter does not have the right
   * format.
   * @throws NullPointerException The <code>name</code> parameter is null.
   */
  private void construct(String name)
      throws MalformedObjectNameException {

    // The name cannot be null
    if (name == null) {
      throw new NullPointerException("name cannot be null");
    }

    // Test if the name is empty
    if (name.length() == 0) {
      // this is equivalent to the whole word query object name.
      _canonicalName = "*:*";
      _kp_array = _Empty_property_array;
      _ca_array = _Empty_property_array;
      _domain_length = 1;
      _propertyList = null;
      _domain_pattern = true;
      _property_list_pattern = true;
      _property_value_pattern = false;
      return;
    }

    // initialize parsing of the string
    final char[] name_chars = name.toCharArray();
    final int len = name_chars.length;
    final char[] canonical_chars = new char[len]; // canonical form will
    // be same length at most
    int cname_index = 0;
    int index = 0;
    char c, c1;

    // parses domain part
    domain_parsing:
    while (index < len) {
      switch (name_chars[index]) {
        case ':':
          _domain_length = index++;
          break domain_parsing;
        case '=':
          // ":" omission check.
          //
          // Although "=" is a valid character in the domain part
          // it is true that it is rarely used in the real world.
          // So check straight away if the ":" has been omitted
          // from the ObjectName. This allows us to provide a more
          // accurate exception message.
          int i = ++index;
          while ((i < len) && (name_chars[i++] != ':')) {
            if (i == len) {
              throw new MalformedObjectNameException(
                  "Domain part must be specified");
            }
          }
          break;
        case '\n':
          throw new MalformedObjectNameException(
              "Invalid character '\\n' in domain name");
        case '*':
        case '?':
          _domain_pattern = true;
          index++;
          break;
        default:
          index++;
          break;
      }
    }

    // check for non-empty properties
    if (index == len) {
      throw new MalformedObjectNameException(
          "Key properties cannot be empty");
    }

    // we have got the domain part, begins building of _canonicalName
    System.arraycopy(name_chars, 0, canonical_chars, 0, _domain_length);
    canonical_chars[_domain_length] = ':';
    cname_index = _domain_length + 1;

    // parses property list
    Property prop;
    Map<String, Property> keys_map = new HashMap<String, Property>();
    String[] keys;
    String key_name;
    boolean quoted_value;
    int property_index = 0;
    int in_index;
    int key_index, key_length, value_index, value_length;

    keys = new String[10];
    _kp_array = new Property[10];
    _property_list_pattern = false;
    _property_value_pattern = false;

    while (index < len) {
      c = name_chars[index];

      // case of pattern properties
      if (c == '*') {
        if (_property_list_pattern) {
          throw new MalformedObjectNameException(
              "Cannot have several '*' characters in pattern " +
                  "property list");
        } else {
          _property_list_pattern = true;
          if ((++index < len) && (name_chars[index] != ',')) {
            throw new MalformedObjectNameException(
                "Invalid character found after '*': end of " +
                    "name or ',' expected");
          } else if (index == len) {
            if (property_index == 0) {
              // empty properties case
              _kp_array = _Empty_property_array;
              _ca_array = _Empty_property_array;
              _propertyList = Collections.emptyMap();
            }
            break;
          } else {
            // correct pattern spec in props, continue
            index++;
            continue;
          }
        }
      }

      // standard property case, key part
      in_index = index;
      key_index = in_index;
      if (name_chars[in_index] == '=') {
        throw new MalformedObjectNameException("Invalid key (empty)");
      }
      while ((in_index < len) && ((c1 = name_chars[in_index++]) != '=')) {
        switch (c1) {
          // '=' considered to introduce value part
          case '*':
          case '?':
          case ',':
          case ':':
          case '\n':
            final String ichar = ((c1 == '\n') ? "\\n" : "" + c1);
            throw new MalformedObjectNameException(
                "Invalid character '" + ichar +
                    "' in key part of property");
        }
      }
      if (name_chars[in_index - 1] != '=') {
        throw new MalformedObjectNameException(
            "Unterminated key property part");
      }
      value_index = in_index; // in_index pointing after '=' char
      key_length = value_index - key_index - 1; // found end of key

      // standard property case, value part
      boolean value_pattern = false;
      if (in_index < len && name_chars[in_index] == '\"') {
        quoted_value = true;
        // the case of quoted value part
        quoted_value_parsing:
        while ((++in_index < len) &&
            ((c1 = name_chars[in_index]) != '\"')) {
          // the case of an escaped character
          if (c1 == '\\') {
            if (++in_index == len) {
              throw new MalformedObjectNameException(
                  "Unterminated quoted value");
            }
            switch (c1 = name_chars[in_index]) {
              case '\\':
              case '\"':
              case '?':
              case '*':
              case 'n':
                break; // valid character
              default:
                throw new MalformedObjectNameException(
                    "Invalid escape sequence '\\" +
                        c1 + "' in quoted value");
            }
          } else if (c1 == '\n') {
            throw new MalformedObjectNameException(
                "Newline in quoted value");
          } else {
            switch (c1) {
              case '?':
              case '*':
                value_pattern = true;
                break;
            }
          }
        }
        if (in_index == len) {
          throw new MalformedObjectNameException(
              "Unterminated quoted value");
        } else {
          value_length = ++in_index - value_index;
        }
      } else {
        // the case of standard value part
        quoted_value = false;
        while ((in_index < len) && ((c1 = name_chars[in_index]) != ',')) {
          switch (c1) {
            // ',' considered to be the value separator
            case '*':
            case '?':
              value_pattern = true;
              in_index++;
              break;
            case '=':
            case ':':
            case '"':
            case '\n':
              final String ichar = ((c1 == '\n') ? "\\n" : "" + c1);
              throw new MalformedObjectNameException(
                  "Invalid character '" + ichar +
                      "' in value part of property");
            default:
              in_index++;
              break;
          }
        }
        value_length = in_index - value_index;
      }

      // Parsed property, checks the end of name
      if (in_index == len - 1) {
        if (quoted_value) {
          throw new MalformedObjectNameException(
              "Invalid ending character `" +
                  name_chars[in_index] + "'");
        } else {
          throw new MalformedObjectNameException(
              "Invalid ending comma");
        }
      } else {
        in_index++;
      }

      // we got the key and value part, prepare a property for this
      if (!value_pattern) {
        prop = new Property(key_index, key_length, value_length);
      } else {
        _property_value_pattern = true;
        prop = new PatternProperty(key_index, key_length, value_length);
      }
      key_name = name.substring(key_index, key_index + key_length);

      if (property_index == keys.length) {
        String[] tmp_string_array = new String[property_index + 10];
        System.arraycopy(keys, 0, tmp_string_array, 0, property_index);
        keys = tmp_string_array;
      }
      keys[property_index] = key_name;

      addProperty(prop, property_index, keys_map, key_name);
      property_index++;
      index = in_index;
    }

    // computes and set canonical name
    setCanonicalName(name_chars, canonical_chars, keys,
        keys_map, cname_index, property_index);
  }

  /**
   * Construct an ObjectName from a domain and a Hashtable.
   *
   * @param domain Domain of the ObjectName.
   * @param props Map containing couples <i>key</i> {@literal ->} <i>value</i>.
   * @throws MalformedObjectNameException The <code>domain</code> contains an illegal character, or
   * one of the keys or values in <code>table</code> contains an illegal character, or one of the
   * values in <code>table</code> does not follow the rules for quoting.
   * @throws NullPointerException One of the parameters is null.
   */
  private void construct(String domain, Map<String, String> props)
      throws MalformedObjectNameException {

    // The domain cannot be null
    if (domain == null) {
      throw new NullPointerException("domain cannot be null");
    }

    // The key property list cannot be null
    if (props == null) {
      throw new NullPointerException("key property list cannot be null");
    }

    // The key property list cannot be empty
    if (props.isEmpty()) {
      throw new MalformedObjectNameException(
          "key property list cannot be empty");
    }

    // checks domain validity
    if (!isDomain(domain)) {
      throw new MalformedObjectNameException("Invalid domain: " + domain);
    }

    // init canonicalname
    final StringBuilder sb = new StringBuilder();
    sb.append(domain).append(':');
    _domain_length = domain.length();

    // allocates the property array
    int nb_props = props.size();
    _kp_array = new Property[nb_props];

    String[] keys = new String[nb_props];
    final Map<String, Property> keys_map = new HashMap<String, Property>();
    Property prop;
    int key_index;
    int i = 0;
    for (Map.Entry<String, String> entry : props.entrySet()) {
      if (sb.length() > 0) {
        sb.append(",");
      }
      String key = entry.getKey();
      String value;
      try {
        value = entry.getValue();
      } catch (ClassCastException e) {
        throw new MalformedObjectNameException(e.getMessage());
      }
      key_index = sb.length();
      checkKey(key);
      sb.append(key);
      keys[i] = key;
      sb.append("=");
      boolean value_pattern = checkValue(value);
      sb.append(value);
      if (!value_pattern) {
        prop = new Property(key_index,
            key.length(),
            value.length());
      } else {
        _property_value_pattern = true;
        prop = new PatternProperty(key_index,
            key.length(),
            value.length());
      }
      addProperty(prop, i, keys_map, key);
      i++;
    }

    // initialize canonical name and data structure
    int len = sb.length();
    char[] initial_chars = new char[len];
    sb.getChars(0, len, initial_chars, 0);
    char[] canonical_chars = new char[len];
    System.arraycopy(initial_chars, 0, canonical_chars, 0,
        _domain_length + 1);
    setCanonicalName(initial_chars, canonical_chars, keys, keys_map,
        _domain_length + 1, _kp_array.length);
  }
  // Category : Instance construction <==============================

  // Category : Internal utilities ------------------------------>

  /**
   * Add passed property to the list at the given index
   * for the passed key name
   */
  private void addProperty(Property prop, int index,
      Map<String, Property> keys_map, String key_name)
      throws MalformedObjectNameException {

    if (keys_map.containsKey(key_name)) {
      throw new
          MalformedObjectNameException("key `" +
          key_name + "' already defined");
    }

    // if no more space for property arrays, have to increase it
    if (index == _kp_array.length) {
      Property[] tmp_prop_array = new Property[index + 10];
      System.arraycopy(_kp_array, 0, tmp_prop_array, 0, index);
      _kp_array = tmp_prop_array;
    }
    _kp_array[index] = prop;
    keys_map.put(key_name, prop);
  }

  /**
   * Sets the canonical name of receiver from input 'specified_chars'
   * array, by filling 'canonical_chars' array with found 'nb-props'
   * properties starting at position 'prop_index'.
   */
  private void setCanonicalName(char[] specified_chars,
      char[] canonical_chars,
      String[] keys, Map<String, Property> keys_map,
      int prop_index, int nb_props) {

    // Sort the list of found properties
    if (_kp_array != _Empty_property_array) {
      String[] tmp_keys = new String[nb_props];
      Property[] tmp_props = new Property[nb_props];

      System.arraycopy(keys, 0, tmp_keys, 0, nb_props);
      Arrays.sort(tmp_keys);
      keys = tmp_keys;
      System.arraycopy(_kp_array, 0, tmp_props, 0, nb_props);
      _kp_array = tmp_props;
      _ca_array = new Property[nb_props];

      // now assigns _ca_array to the sorted list of keys
      // (there cannot be two identical keys in an objectname.
      for (int i = 0; i < nb_props; i++) {
        _ca_array[i] = keys_map.get(keys[i]);
      }

      // now we build the canonical name and set begin indexes of
      // properties to reflect canonical form
      int last_index = nb_props - 1;
      int prop_len;
      Property prop;
      for (int i = 0; i <= last_index; i++) {
        prop = _ca_array[i];
        // length of prop including '=' char
        prop_len = prop._key_length + prop._value_length + 1;
        System.arraycopy(specified_chars, prop._key_index,
            canonical_chars, prop_index, prop_len);
        prop.setKeyIndex(prop_index);
        prop_index += prop_len;
        if (i != last_index) {
          canonical_chars[prop_index] = ',';
          prop_index++;
        }
      }
    }

    // terminate canonicalname with '*' in case of pattern
    if (_property_list_pattern) {
      if (_kp_array != _Empty_property_array) {
        canonical_chars[prop_index++] = ',';
      }
      canonical_chars[prop_index++] = '*';
    }

    // we now build the canonicalname string
    _canonicalName = (new String(canonical_chars, 0, prop_index)).intern();
  }

  /**
   * Parse a key.
   * <pre>final int endKey=parseKey(s,startKey);</pre>
   * <p>key starts at startKey (included), and ends at endKey (excluded).
   * If (startKey == endKey), then the key is empty.
   *
   * @param s The char array of the original string.
   * @param startKey index at which to begin parsing.
   * @return The index following the last character of the key.
   **/
  private static int parseKey(final char[] s, final int startKey)
      throws MalformedObjectNameException {
    int next = startKey;
    int endKey = startKey;
    final int len = s.length;
    while (next < len) {
      final char k = s[next++];
      switch (k) {
        case '*':
        case '?':
        case ',':
        case ':':
        case '\n':
          final String ichar = ((k == '\n') ? "\\n" : "" + k);
          throw new
              MalformedObjectNameException("Invalid character in key: `"
              + ichar + "'");
        case '=':
          // we got the key.
          endKey = next - 1;
          break;
        default:
          if (next < len) {
            continue;
          } else {
            endKey = next;
          }
      }
      break;
    }
    return endKey;
  }

  /**
   * Parse a value.
   * <pre>final int endVal=parseValue(s,startVal);</pre>
   * <p>value starts at startVal (included), and ends at endVal (excluded).
   * If (startVal == endVal), then the key is empty.
   *
   * @param s The char array of the original string.
   * @param startValue index at which to begin parsing.
   * @return The first element of the int array indicates the index following the last character of
   * the value. The second element of the int array indicates that the value is a pattern when its
   * value equals 1.
   **/
  private static int[] parseValue(final char[] s, final int startValue)
      throws MalformedObjectNameException {

    boolean value_pattern = false;

    int next = startValue;
    int endValue = startValue;

    final int len = s.length;
    final char q = s[startValue];

    if (q == '"') {
      // quoted value
      if (++next == len) {
        throw new
            MalformedObjectNameException("Invalid quote");
      }
      while (next < len) {
        char last = s[next];
        if (last == '\\') {
          if (++next == len) {
            throw new
                MalformedObjectNameException(
                "Invalid unterminated quoted character sequence");
          }
          last = s[next];
          switch (last) {
            case '\\':
            case '?':
            case '*':
            case 'n':
              break;
            case '\"':
              // We have an escaped quote. If this escaped
              // quote is the last character, it does not
              // qualify as a valid termination quote.
              //
              if (next + 1 == len) {
                throw new
                    MalformedObjectNameException(
                    "Missing termination quote");
              }
              break;
            default:
              throw new
                  MalformedObjectNameException(
                  "Invalid quoted character sequence '\\" +
                      last + "'");
          }
        } else if (last == '\n') {
          throw new MalformedObjectNameException(
              "Newline in quoted value");
        } else if (last == '\"') {
          next++;
          break;
        } else {
          switch (last) {
            case '?':
            case '*':
              value_pattern = true;
              break;
          }
        }
        next++;

        // Check that last character is a termination quote.
        // We have already handled the case were the last
        // character is an escaped quote earlier.
        //
        if ((next >= len) && (last != '\"')) {
          throw new
              MalformedObjectNameException("Missing termination quote");
        }
      }
      endValue = next;
      if (next < len) {
        if (s[next++] != ',') {
          throw new
              MalformedObjectNameException("Invalid quote");
        }
      }
    } else {
      // Non quoted value.
      while (next < len) {
        final char v = s[next++];
        switch (v) {
          case '*':
          case '?':
            value_pattern = true;
            if (next < len) {
              continue;
            } else {
              endValue = next;
            }
            break;
          case '=':
          case ':':
          case '\n':
            final String ichar = ((v == '\n') ? "\\n" : "" + v);
            throw new
                MalformedObjectNameException("Invalid character `" +
                ichar + "' in value");
          case ',':
            endValue = next - 1;
            break;
          default:
            if (next < len) {
              continue;
            } else {
              endValue = next;
            }
        }
        break;
      }
    }
    return new int[]{endValue, value_pattern ? 1 : 0};
  }

  /**
   * Check if the supplied value is a valid value.
   *
   * @return true if the value is a pattern, otherwise false.
   */
  private static boolean checkValue(String val)
      throws MalformedObjectNameException {

    if (val == null) {
      throw new
          NullPointerException("Invalid value (null)");
    }

    final int len = val.length();
    if (len == 0) {
      return false;
    }

    final char[] s = val.toCharArray();
    final int[] result = parseValue(s, 0);
    final int endValue = result[0];
    final boolean value_pattern = result[1] == 1;
    if (endValue < len) {
      throw new
          MalformedObjectNameException("Invalid character in value: `" +
          s[endValue] + "'");
    }
    return value_pattern;
  }

  /**
   * Check if the supplied key is a valid key.
   */
  private static void checkKey(String key)
      throws MalformedObjectNameException {

    if (key == null) {
      throw new
          NullPointerException("Invalid key (null)");
    }

    final int len = key.length();
    if (len == 0) {
      throw new
          MalformedObjectNameException("Invalid key (empty)");
    }
    final char[] k = key.toCharArray();
    final int endKey = parseKey(k, 0);
    if (endKey < len) {
      throw new
          MalformedObjectNameException("Invalid character in value: `" +
          k[endKey] + "'");
    }
  }

  // Category : Internal utilities <==============================

  // Category : Internal accessors ------------------------------>

  /**
   * Check if domain is a valid domain.  Set _domain_pattern if appropriate.
   */
  private boolean isDomain(String domain) {
    if (domain == null) {
      return true;
    }
    final int len = domain.length();
    int next = 0;
    while (next < len) {
      final char c = domain.charAt(next++);
      switch (c) {
        case ':':
        case '\n':
          return false;
        case '*':
        case '?':
          _domain_pattern = true;
          break;
      }
    }
    return true;
  }

  // Category : Internal accessors <==============================

  // Category : Serialization ----------------------------------->

  /**
   * Deserializes an {@link ObjectName} from an {@link ObjectInputStream}.
   *
   * @serialData <ul> <li>In the current serial form (value of property <code>jmx.serial.form</code>
   * differs from <code>1.0</code>): the string &quot;&lt;domain&gt;:&lt;properties&gt;&lt;wild&gt;&quot;,
   * where: <ul> <li>&lt;domain&gt; represents the domain part of the {@link ObjectName}</li>
   * <li>&lt;properties&gt; represents the list of properties, as returned by {@link
   * #getKeyPropertyListString} <li>&lt;wild&gt; is empty if not <code>isPropertyPattern</code>, or
   * is the character "<code>*</code>" if <code>isPropertyPattern</code> and &lt;properties&gt; is
   * empty, or is "<code>,*</code>" if <code>isPropertyPattern</code> and &lt;properties&gt; is not
   * empty. </li> </ul> The intent is that this string could be supplied to the {@link
   * #ObjectName(String)} constructor to produce an equivalent {@link ObjectName}. </li> <li>In the
   * old serial form (value of property <code>jmx.serial.form</code> is <code>1.0</code>):
   * &lt;domain&gt; &lt;propertyList&gt; &lt;propertyListString&gt; &lt;canonicalName&gt;
   * &lt;pattern&gt; &lt;propertyPattern&gt;, where: <ul> <li>&lt;domain&gt; represents the domain
   * part of the {@link ObjectName}</li> <li>&lt;propertyList&gt; is the {@link Hashtable} that
   * contains all the pairs (key,value) for this {@link ObjectName}</li>
   * <li>&lt;propertyListString&gt; is the {@link String} representation of the list of properties
   * in any order (not mandatorily a canonical representation) </li> <li>&lt;canonicalName&gt; is
   * the {@link String} containing this {@link ObjectName}'s canonical name</li> <li>&lt;pattern&gt;
   * is a boolean which is <code>true</code> if this {@link ObjectName} contains a pattern</li>
   * <li>&lt;propertyPattern&gt; is a boolean which is <code>true</code> if this {@link ObjectName}
   * contains a pattern in the list of properties</li> </ul> </li> </ul>
   */
  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {

    String cn;
    if (compat) {
      // Read an object serialized in the old serial form
      //
      //in.defaultReadObject();
      final ObjectInputStream.GetField fields = in.readFields();
      String propListString =
          (String) fields.get("propertyListString", "");

      // 6616825: take care of property patterns
      final boolean propPattern =
          fields.get("propertyPattern", false);
      if (propPattern) {
        propListString =
            (propListString.length() == 0 ? "*" : (propListString + ",*"));
      }

      cn = (String) fields.get("domain", "default") +
          ":" + propListString;
    } else {
      // Read an object serialized in the new serial form
      //
      in.defaultReadObject();
      cn = (String) in.readObject();
    }

    try {
      construct(cn);
    } catch (NullPointerException e) {
      throw new InvalidObjectException(e.toString());
    } catch (MalformedObjectNameException e) {
      throw new InvalidObjectException(e.toString());
    }
  }


  /**
   * Serializes an {@link ObjectName} to an {@link ObjectOutputStream}.
   *
   * @serialData <ul> <li>In the current serial form (value of property <code>jmx.serial.form</code>
   * differs from <code>1.0</code>): the string &quot;&lt;domain&gt;:&lt;properties&gt;&lt;wild&gt;&quot;,
   * where: <ul> <li>&lt;domain&gt; represents the domain part of the {@link ObjectName}</li>
   * <li>&lt;properties&gt; represents the list of properties, as returned by {@link
   * #getKeyPropertyListString} <li>&lt;wild&gt; is empty if not <code>isPropertyPattern</code>, or
   * is the character "<code>*</code>" if this <code>isPropertyPattern</code> and &lt;properties&gt;
   * is empty, or is "<code>,*</code>" if <code>isPropertyPattern</code> and &lt;properties&gt; is
   * not empty. </li> </ul> The intent is that this string could be supplied to the {@link
   * #ObjectName(String)} constructor to produce an equivalent {@link ObjectName}. </li> <li>In the
   * old serial form (value of property <code>jmx.serial.form</code> is <code>1.0</code>):
   * &lt;domain&gt; &lt;propertyList&gt; &lt;propertyListString&gt; &lt;canonicalName&gt;
   * &lt;pattern&gt; &lt;propertyPattern&gt;, where: <ul> <li>&lt;domain&gt; represents the domain
   * part of the {@link ObjectName}</li> <li>&lt;propertyList&gt; is the {@link Hashtable} that
   * contains all the pairs (key,value) for this {@link ObjectName}</li>
   * <li>&lt;propertyListString&gt; is the {@link String} representation of the list of properties
   * in any order (not mandatorily a canonical representation) </li> <li>&lt;canonicalName&gt; is
   * the {@link String} containing this {@link ObjectName}'s canonical name</li> <li>&lt;pattern&gt;
   * is a boolean which is <code>true</code> if this {@link ObjectName} contains a pattern</li>
   * <li>&lt;propertyPattern&gt; is a boolean which is <code>true</code> if this {@link ObjectName}
   * contains a pattern in the list of properties</li> </ul> </li> </ul>
   */
  private void writeObject(ObjectOutputStream out)
      throws IOException {

    if (compat) {
      // Serializes this instance in the old serial form
      // Read CR 6441274 before making any changes to this code
      ObjectOutputStream.PutField fields = out.putFields();
      fields.put("domain", _canonicalName.substring(0, _domain_length));
      fields.put("propertyList", getKeyPropertyList());
      fields.put("propertyListString", getKeyPropertyListString());
      fields.put("canonicalName", _canonicalName);
      fields.put("pattern", (_domain_pattern || _property_list_pattern));
      fields.put("propertyPattern", _property_list_pattern);
      out.writeFields();
    } else {
      // Serializes this instance in the new serial form
      //
      out.defaultWriteObject();
      out.writeObject(getSerializedNameString());
    }
  }

  //  Category : Serialization <===================================

  // Private methods <========================================

  // Public methods ---------------------------------------->

  // Category : ObjectName Construction ------------------------------>

  /**
   * <p>Return an instance of ObjectName that can be used anywhere
   * an object obtained with {@link #ObjectName(String) new
   * ObjectName(name)} can be used.  The returned object may be of
   * a subclass of ObjectName.  Calling this method twice with the
   * same parameters may return the same object or two equal but
   * not identical objects.</p>
   *
   * @param name A string representation of the object name.
   * @return an ObjectName corresponding to the given String.
   * @throws MalformedObjectNameException The string passed as a parameter does not have the right
   * format.
   * @throws NullPointerException The <code>name</code> parameter is null.
   */
  public static ObjectName getInstance(String name)
      throws MalformedObjectNameException, NullPointerException {
    return new ObjectName(name);
  }

  /**
   * <p>Return an instance of ObjectName that can be used anywhere
   * an object obtained with {@link #ObjectName(String, String,
   * String) new ObjectName(domain, key, value)} can be used.  The
   * returned object may be of a subclass of ObjectName.  Calling
   * this method twice with the same parameters may return the same
   * object or two equal but not identical objects.</p>
   *
   * @param domain The domain part of the object name.
   * @param key The attribute in the key property of the object name.
   * @param value The value in the key property of the object name.
   * @return an ObjectName corresponding to the given domain, key, and value.
   * @throws MalformedObjectNameException The <code>domain</code>, <code>key</code>, or
   * <code>value</code> contains an illegal character, or <code>value</code> does not follow the
   * rules for quoting.
   * @throws NullPointerException One of the parameters is null.
   */
  public static ObjectName getInstance(String domain, String key,
      String value)
      throws MalformedObjectNameException {
    return new ObjectName(domain, key, value);
  }

  /**
   * <p>Return an instance of ObjectName that can be used anywhere
   * an object obtained with {@link #ObjectName(String, Hashtable)
   * new ObjectName(domain, table)} can be used.  The returned
   * object may be of a subclass of ObjectName.  Calling this method
   * twice with the same parameters may return the same object or
   * two equal but not identical objects.</p>
   *
   * @param domain The domain part of the object name.
   * @param table A hash table containing one or more key properties.  The key of each entry in the
   * table is the key of a key property in the object name.  The associated value in the table is
   * the associated value in the object name.
   * @return an ObjectName corresponding to the given domain and key mappings.
   * @throws MalformedObjectNameException The <code>domain</code> contains an illegal character, or
   * one of the keys or values in <code>table</code> contains an illegal character, or one of the
   * values in <code>table</code> does not follow the rules for quoting.
   * @throws NullPointerException One of the parameters is null.
   */
  public static ObjectName getInstance(String domain,
      Hashtable<String, String> table)
      throws MalformedObjectNameException {
    return new ObjectName(domain, table);
  }

  /**
   * <p>Return an instance of ObjectName that can be used anywhere
   * the given object can be used.  The returned object may be of a
   * subclass of ObjectName.  If <code>name</code> is of a subclass
   * of ObjectName, it is not guaranteed that the returned object
   * will be of the same class.</p>
   *
   * <p>The returned value may or may not be identical to
   * <code>name</code>.  Calling this method twice with the same
   * parameters may return the same object or two equal but not
   * identical objects.</p>
   *
   * <p>Since ObjectName is immutable, it is not usually useful to
   * make a copy of an ObjectName.  The principal use of this method
   * is to guard against a malicious caller who might pass an
   * instance of a subclass with surprising behavior to sensitive
   * code.  Such code can call this method to obtain an ObjectName
   * that is known not to have surprising behavior.</p>
   *
   * @param name an instance of the ObjectName class or of a subclass
   * @return an instance of ObjectName or a subclass that is known to have the same semantics.  If
   * <code>name</code> respects the semantics of ObjectName, then the returned object is equal
   * (though not necessarily identical) to <code>name</code>.
   * @throws NullPointerException The <code>name</code> is null.
   */
  public static ObjectName getInstance(ObjectName name) {
    if (name.getClass().equals(ObjectName.class)) {
      return name;
    }
    return Util.newObjectName(name.getSerializedNameString());
  }

  /**
   * Construct an object name from the given string.
   *
   * @param name A string representation of the object name.
   * @throws MalformedObjectNameException The string passed as a parameter does not have the right
   * format.
   * @throws NullPointerException The <code>name</code> parameter is null.
   */
  public ObjectName(String name)
      throws MalformedObjectNameException {
    construct(name);
  }

  /**
   * Construct an object name with exactly one key property.
   *
   * @param domain The domain part of the object name.
   * @param key The attribute in the key property of the object name.
   * @param value The value in the key property of the object name.
   * @throws MalformedObjectNameException The <code>domain</code>, <code>key</code>, or
   * <code>value</code> contains an illegal character, or <code>value</code> does not follow the
   * rules for quoting.
   * @throws NullPointerException One of the parameters is null.
   */
  public ObjectName(String domain, String key, String value)
      throws MalformedObjectNameException {
    // If key or value are null a NullPointerException
    // will be thrown by the put method in Hashtable.
    //
    Map<String, String> table = Collections.singletonMap(key, value);
    construct(domain, table);
  }

  /**
   * Construct an object name with several key properties from a Hashtable.
   *
   * @param domain The domain part of the object name.
   * @param table A hash table containing one or more key properties.  The key of each entry in the
   * table is the key of a key property in the object name.  The associated value in the table is
   * the associated value in the object name.
   * @throws MalformedObjectNameException The <code>domain</code> contains an illegal character, or
   * one of the keys or values in <code>table</code> contains an illegal character, or one of the
   * values in <code>table</code> does not follow the rules for quoting.
   * @throws NullPointerException One of the parameters is null.
   */
  public ObjectName(String domain, Hashtable<String, String> table)
      throws MalformedObjectNameException {
    construct(domain, table);
        /* The exception for when a key or value in the table is not a
           String is now ClassCastException rather than
           MalformedObjectNameException.  This was not previously
           specified.  */
  }

  // Category : ObjectName Construction <==============================

  // Category : Getter methods ------------------------------>

  /**
   * Checks whether the object name is a pattern.
   * <p>
   * An object name is a pattern if its domain contains a
   * wildcard or if the object name is a property pattern.
   *
   * @return True if the name is a pattern, otherwise false.
   */
  public boolean isPattern() {
    return (_domain_pattern ||
        _property_list_pattern ||
        _property_value_pattern);
  }

  /**
   * Checks whether the object name is a pattern on the domain part.
   *
   * @return True if the name is a domain pattern, otherwise false.
   */
  public boolean isDomainPattern() {
    return _domain_pattern;
  }

  /**
   * Checks whether the object name is a pattern on the key properties.
   * <p>
   * An object name is a pattern on the key properties if it is a
   * pattern on the key property list (e.g. "d:k=v,*") or on the
   * property values (e.g. "d:k=*") or on both (e.g. "d:k=*,*").
   *
   * @return True if the name is a property pattern, otherwise false.
   */
  public boolean isPropertyPattern() {
    return _property_list_pattern || _property_value_pattern;
  }

  /**
   * Checks whether the object name is a pattern on the key property list.
   * <p>
   * For example, "d:k=v,*" and "d:k=*,*" are key property list patterns
   * whereas "d:k=*" is not.
   *
   * @return True if the name is a property list pattern, otherwise false.
   * @since 1.6
   */
  public boolean isPropertyListPattern() {
    return _property_list_pattern;
  }

  /**
   * Checks whether the object name is a pattern on the value part
   * of at least one of the key properties.
   * <p>
   * For example, "d:k=*" and "d:k=*,*" are property value patterns
   * whereas "d:k=v,*" is not.
   *
   * @return True if the name is a property value pattern, otherwise false.
   * @since 1.6
   */
  public boolean isPropertyValuePattern() {
    return _property_value_pattern;
  }

  /**
   * Checks whether the value associated with a key in a key
   * property is a pattern.
   *
   * @param property The property whose value is to be checked.
   * @return True if the value associated with the given key property is a pattern, otherwise false.
   * @throws NullPointerException If <code>property</code> is null.
   * @throws IllegalArgumentException If <code>property</code> is not a valid key property for this
   * ObjectName.
   * @since 1.6
   */
  public boolean isPropertyValuePattern(String property) {
    if (property == null) {
      throw new NullPointerException("key property can't be null");
    }
    for (int i = 0; i < _ca_array.length; i++) {
      Property prop = _ca_array[i];
      String key = prop.getKeyString(_canonicalName);
      if (key.equals(property)) {
        return (prop instanceof PatternProperty);
      }
    }
    throw new IllegalArgumentException("key property not found");
  }

  /**
   * <p>Returns the canonical form of the name; that is, a string
   * representation where the properties are sorted in lexical
   * order.</p>
   *
   * <p>More precisely, the canonical form of the name is a String
   * consisting of the <em>domain part</em>, a colon
   * (<code>:</code>), the <em>canonical key property list</em>, and
   * a <em>pattern indication</em>.</p>
   *
   * <p>The <em>canonical key property list</em> is the same string
   * as described for {@link #getCanonicalKeyPropertyListString()}.</p>
   *
   * <p>The <em>pattern indication</em> is:
   * <ul>
   * <li>empty for an ObjectName
   * that is not a property list pattern;
   * <li>an asterisk for an ObjectName
   * that is a property list pattern with no keys; or
   * <li>a comma and an
   * asterisk (<code>,*</code>) for an ObjectName that is a property
   * list pattern with at least one key.
   * </ul>
   *
   * @return The canonical form of the name.
   */
  public String getCanonicalName() {
    return _canonicalName;
  }

  /**
   * Returns the domain part.
   *
   * @return The domain.
   */
  public String getDomain() {
    return _canonicalName.substring(0, _domain_length);
  }

  /**
   * Obtains the value associated with a key in a key property.
   *
   * @param property The property whose value is to be obtained.
   * @return The value of the property, or null if there is no such property in this ObjectName.
   * @throws NullPointerException If <code>property</code> is null.
   */
  public String getKeyProperty(String property) {
    return _getKeyPropertyList().get(property);
  }

  /**
   * <p>Returns the key properties as a Map.  The returned
   * value is a Map in which each key is a key in the
   * ObjectName's key property list and each value is the associated
   * value.</p>
   *
   * <p>The returned value must not be modified.</p>
   *
   * @return The table of key properties.
   */
  private Map<String, String> _getKeyPropertyList() {
    synchronized (this) {
      if (_propertyList == null) {
        // build (lazy eval) the property list from the canonical
        // properties array
        _propertyList = new HashMap<String, String>();
        int len = _ca_array.length;
        Property prop;
        for (int i = len - 1; i >= 0; i--) {
          prop = _ca_array[i];
          _propertyList.put(prop.getKeyString(_canonicalName),
              prop.getValueString(_canonicalName));
        }
      }
    }
    return _propertyList;
  }

  /**
   * <p>Returns the key properties as a Hashtable.  The returned
   * value is a Hashtable in which each key is a key in the
   * ObjectName's key property list and each value is the associated
   * value.</p>
   *
   * <p>The returned value may be unmodifiable.  If it is
   * modifiable, changing it has no effect on this ObjectName.</p>
   *
   * @return The table of key properties.
   */
  // CR 6441274 depends on the modification property defined above
  public Hashtable<String, String> getKeyPropertyList() {
    return new Hashtable<String, String>(_getKeyPropertyList());
  }

  /**
   * <p>Returns a string representation of the list of key
   * properties specified at creation time.  If this ObjectName was
   * constructed with the constructor {@link #ObjectName(String)},
   * the key properties in the returned String will be in the same
   * order as in the argument to the constructor.</p>
   *
   * @return The key property list string.  This string is independent of whether the ObjectName is
   * a pattern.
   */
  public String getKeyPropertyListString() {
    // BEWARE : we rebuild the propertyliststring at each call !!
    if (_kp_array.length == 0) {
      return "";
    }

    // the size of the string is the canonical one minus domain
    // part and pattern part
    final int total_size = _canonicalName.length() - _domain_length - 1
        - (_property_list_pattern ? 2 : 0);

    final char[] dest_chars = new char[total_size];
    final char[] value = _canonicalName.toCharArray();
    writeKeyPropertyListString(value, dest_chars, 0);
    return new String(dest_chars);
  }

  /**
   * <p>Returns the serialized string of the ObjectName.
   * properties specified at creation time.  If this ObjectName was
   * constructed with the constructor {@link #ObjectName(String)},
   * the key properties in the returned String will be in the same
   * order as in the argument to the constructor.</p>
   *
   * @return The key property list string.  This string is independent of whether the ObjectName is
   * a pattern.
   */
  private String getSerializedNameString() {

    // the size of the string is the canonical one
    final int total_size = _canonicalName.length();
    final char[] dest_chars = new char[total_size];
    final char[] value = _canonicalName.toCharArray();
    final int offset = _domain_length + 1;

    // copy "domain:" into dest_chars
    //
    System.arraycopy(value, 0, dest_chars, 0, offset);

    // Add property list string
    final int end = writeKeyPropertyListString(value, dest_chars, offset);

    // Add ",*" if necessary
    if (_property_list_pattern) {
      if (end == offset) {
        // Property list string is empty.
        dest_chars[end] = '*';
      } else {
        // Property list string is not empty.
        dest_chars[end] = ',';
        dest_chars[end + 1] = '*';
      }
    }

    return new String(dest_chars);
  }

  /**
   * <p>Write a string representation of the list of key
   * properties specified at creation time in the given array, starting
   * at the specified offset.  If this ObjectName was
   * constructed with the constructor {@link #ObjectName(String)},
   * the key properties in the returned String will be in the same
   * order as in the argument to the constructor.</p>
   *
   * @return offset + #of chars written
   */
  private int writeKeyPropertyListString(char[] canonicalChars,
      char[] data, int offset) {
    if (_kp_array.length == 0) {
      return offset;
    }

    final char[] dest_chars = data;
    final char[] value = canonicalChars;

    int index = offset;
    final int len = _kp_array.length;
    final int last = len - 1;
    for (int i = 0; i < len; i++) {
      final Property prop = _kp_array[i];
      final int prop_len = prop._key_length + prop._value_length + 1;
      System.arraycopy(value, prop._key_index, dest_chars, index,
          prop_len);
      index += prop_len;
      if (i < last) {
        dest_chars[index++] = ',';
      }
    }
    return index;
  }


  /**
   * Returns a string representation of the list of key properties,
   * in which the key properties are sorted in lexical order. This
   * is used in lexicographic comparisons performed in order to
   * select MBeans based on their key property list.  Lexical order
   * is the order implied by {@link String#compareTo(String)
   * String.compareTo(String)}.
   *
   * @return The canonical key property list string.  This string is independent of whether the
   * ObjectName is a pattern.
   */
  public String getCanonicalKeyPropertyListString() {
    if (_ca_array.length == 0) {
      return "";
    }

    int len = _canonicalName.length();
    if (_property_list_pattern) {
      len -= 2;
    }
    return _canonicalName.substring(_domain_length + 1, len);
  }
  // Category : Getter methods <===================================

  // Category : Utilities ---------------------------------------->

  /**
   * <p>Returns a string representation of the object name.  The
   * format of this string is not specified, but users can expect
   * that two ObjectNames return the same string if and only if they
   * are equal.</p>
   *
   * @return a string representation of this object name.
   */
  @Override
  public String toString() {
    return getSerializedNameString();
  }

  /**
   * Compares the current object name with another object name.  Two
   * ObjectName instances are equal if and only if their canonical
   * forms are equal.  The canonical form is the string described
   * for {@link #getCanonicalName()}.
   *
   * @param object The object name that the current object name is to be compared with.
   * @return True if <code>object</code> is an ObjectName whose canonical form is equal to that of
   * this ObjectName.
   */
  @Override
  public boolean equals(Object object) {

    // same object case
    if (this == object) {
      return true;
    }

    // object is not an object name case
    if (!(object instanceof ObjectName)) {
      return false;
    }

    // equality when canonical names are the same
    // (because usage of intern())
    ObjectName on = (ObjectName) object;
    String on_string = on._canonicalName;
    if (_canonicalName == on_string) {
      return true;  // ES: OK
    }

    // Because we are sharing canonical form between object names,
    // we have finished the comparison at this stage ==> unequal
    return false;
  }

  /**
   * Returns a hash code for this object name.
   */
  @Override
  public int hashCode() {
    return _canonicalName.hashCode();
  }

  /**
   * <p>Returns a quoted form of the given String, suitable for
   * inclusion in an ObjectName.  The returned value can be used as
   * the value associated with a key in an ObjectName.  The String
   * <code>s</code> may contain any character.  Appropriate quoting
   * ensures that the returned value is legal in an ObjectName.</p>
   *
   * <p>The returned value consists of a quote ('"'), a sequence of
   * characters corresponding to the characters of <code>s</code>,
   * and another quote.  Characters in <code>s</code> appear
   * unchanged within the returned value except:</p>
   *
   * <ul>
   * <li>A quote ('"') is replaced by a backslash (\) followed by a quote.</li>
   * <li>An asterisk ('*') is replaced by a backslash (\) followed by an
   * asterisk.</li>
   * <li>A question mark ('?') is replaced by a backslash (\) followed by
   * a question mark.</li>
   * <li>A backslash ('\') is replaced by two backslashes.</li>
   * <li>A newline character (the character '\n' in Java) is replaced
   * by a backslash followed by the character '\n'.</li>
   * </ul>
   *
   * @param s the String to be quoted.
   * @return the quoted String.
   * @throws NullPointerException if <code>s</code> is null.
   */
  public static String quote(String s) {
    final StringBuilder buf = new StringBuilder("\"");
    final int len = s.length();
    for (int i = 0; i < len; i++) {
      char c = s.charAt(i);
      switch (c) {
        case '\n':
          c = 'n';
          buf.append('\\');
          break;
        case '\\':
        case '\"':
        case '*':
        case '?':
          buf.append('\\');
          break;
      }
      buf.append(c);
    }
    buf.append('"');
    return buf.toString();
  }

  /**
   * <p>Returns an unquoted form of the given String.  If
   * <code>q</code> is a String returned by {@link #quote quote(s)},
   * then <code>unquote(q).equals(s)</code>.  If there is no String
   * <code>s</code> for which <code>quote(s).equals(q)</code>, then
   * unquote(q) throws an IllegalArgumentException.</p>
   *
   * <p>These rules imply that there is a one-to-one mapping between
   * quoted and unquoted forms.</p>
   *
   * @param q the String to be unquoted.
   * @return the unquoted String.
   * @throws IllegalArgumentException if <code>q</code> could not have been returned by the {@link
   * #quote} method, for instance if it does not begin and end with a quote (").
   * @throws NullPointerException if <code>q</code> is null.
   */
  public static String unquote(String q) {
    final StringBuilder buf = new StringBuilder();
    final int len = q.length();
    if (len < 2 || q.charAt(0) != '"' || q.charAt(len - 1) != '"') {
      throw new IllegalArgumentException("Argument not quoted");
    }
    for (int i = 1; i < len - 1; i++) {
      char c = q.charAt(i);
      if (c == '\\') {
        if (i == len - 2) {
          throw new IllegalArgumentException("Trailing backslash");
        }
        c = q.charAt(++i);
        switch (c) {
          case 'n':
            c = '\n';
            break;
          case '\\':
          case '\"':
          case '*':
          case '?':
            break;
          default:
            throw new IllegalArgumentException(
                "Bad character '" + c + "' after backslash");
        }
      } else {
        switch (c) {
          case '*':
          case '?':
          case '\"':
          case '\n':
            throw new IllegalArgumentException(
                "Invalid unescaped character '" + c +
                    "' in the string to unquote");
        }
      }
      buf.append(c);
    }
    return buf.toString();
  }

  /**
   * Defines the wildcard "*:*" ObjectName.
   *
   * @since 1.6
   */
  public static final ObjectName WILDCARD = Util.newObjectName("*:*");

  // Category : Utilities <===================================

  // Category : QueryExp Interface ---------------------------------------->

  /**
   * <p>Test whether this ObjectName, which may be a pattern,
   * matches another ObjectName.  If <code>name</code> is a pattern,
   * the result is false.  If this ObjectName is a pattern, the
   * result is true if and only if <code>name</code> matches the
   * pattern.  If neither this ObjectName nor <code>name</code> is
   * a pattern, the result is true if and only if the two
   * ObjectNames are equal as described for the {@link
   * #equals(Object)} method.</p>
   *
   * @param name The name of the MBean to compare to.
   * @return True if <code>name</code> matches this ObjectName.
   * @throws NullPointerException if <code>name</code> is null.
   */
  public boolean apply(ObjectName name) {

    if (name == null) {
      throw new NullPointerException();
    }

    if (name._domain_pattern ||
        name._property_list_pattern ||
        name._property_value_pattern) {
      return false;
    }

    // No pattern
    if (!_domain_pattern &&
        !_property_list_pattern &&
        !_property_value_pattern) {
      return _canonicalName.equals(name._canonicalName);
    }

    return matchDomains(name) && matchKeys(name);
  }

  private final boolean matchDomains(ObjectName name) {
    if (_domain_pattern) {
      // wildmatch domains
      // This ObjectName is the pattern
      // The other ObjectName is the string.
      return Util.wildmatch(name.getDomain(), getDomain());
    }
    return getDomain().equals(name.getDomain());
  }

  private final boolean matchKeys(ObjectName name) {
    // If key property value pattern but not key property list
    // pattern, then the number of key properties must be equal
    //
    if (_property_value_pattern &&
        !_property_list_pattern &&
        (name._ca_array.length != _ca_array.length)) {
      return false;
    }

    // If key property value pattern or key property list pattern,
    // then every property inside pattern should exist in name
    //
    if (_property_value_pattern || _property_list_pattern) {
      final Map<String, String> nameProps = name._getKeyPropertyList();
      final Property[] props = _ca_array;
      final String cn = _canonicalName;
      for (int i = props.length - 1; i >= 0; i--) {
        // Find value in given object name for key at current
        // index in receiver
        //
        final Property p = props[i];
        final String k = p.getKeyString(cn);
        final String v = nameProps.get(k);
        // Did we find a value for this key ?
        //
        if (v == null) {
          return false;
        }
        // If this property is ok (same key, same value), go to next
        //
        if (_property_value_pattern && (p instanceof PatternProperty)) {
          // wildmatch key property values
          // p is the property pattern, v is the string
          if (Util.wildmatch(v, p.getValueString(cn))) {
            continue;
          } else {
            return false;
          }
        }
        if (v.equals(p.getValueString(cn))) {
          continue;
        }
        return false;
      }
      return true;
    }

    // If no pattern, then canonical names must be equal
    //
    final String p1 = name.getCanonicalKeyPropertyListString();
    final String p2 = getCanonicalKeyPropertyListString();
    return (p1.equals(p2));
  }

  /* Method inherited from QueryExp, no implementation needed here
     because ObjectName is not relative to an MBeanServer and does
     not contain a subquery.
  */
  public void setMBeanServer(MBeanServer mbs) {
  }

  // Category : QueryExp Interface <=========================

  // Category : Comparable Interface ---------------------------------------->

  /**
   * <p>Compares two ObjectName instances. The ordering relation between
   * ObjectNames is not completely specified but is intended to be such
   * that a sorted list of ObjectNames will appear in an order that is
   * convenient for a person to read.</p>
   *
   * <p>In particular, if the two ObjectName instances have different
   * domains then their order is the lexicographical order of the domains.
   * The ordering of the key property list remains unspecified.</p>
   *
   * <p>For example, the ObjectName instances below:</p>
   * <ul>
   * <li>Shapes:type=Square,name=3</li>
   * <li>Colors:type=Red,name=2</li>
   * <li>Shapes:type=Triangle,side=isosceles,name=2</li>
   * <li>Colors:type=Red,name=1</li>
   * <li>Shapes:type=Square,name=1</li>
   * <li>Colors:type=Blue,name=1</li>
   * <li>Shapes:type=Square,name=2</li>
   * <li>JMImplementation:type=MBeanServerDelegate</li>
   * <li>Shapes:type=Triangle,side=scalene,name=1</li>
   * </ul>
   * <p>could be ordered as follows:</p>
   * <ul>
   * <li>Colors:type=Blue,name=1</li>
   * <li>Colors:type=Red,name=1</li>
   * <li>Colors:type=Red,name=2</li>
   * <li>JMImplementation:type=MBeanServerDelegate</li>
   * <li>Shapes:type=Square,name=1</li>
   * <li>Shapes:type=Square,name=2</li>
   * <li>Shapes:type=Square,name=3</li>
   * <li>Shapes:type=Triangle,side=scalene,name=1</li>
   * <li>Shapes:type=Triangle,side=isosceles,name=2</li>
   * </ul>
   *
   * @param name the ObjectName to be compared.
   * @return a negative integer, zero, or a positive integer as this ObjectName is less than, equal
   * to, or greater than the specified ObjectName.
   * @since 1.6
   */
  public int compareTo(ObjectName name) {
    // Quick optimization:
    //
    if (name == this) {
      return 0;
    }

    // (1) Compare domains
    //
    int domainValue = this.getDomain().compareTo(name.getDomain());
    if (domainValue != 0) {
      return domainValue;
    }

    // (2) Compare "type=" keys
    //
    // Within a given domain, all names with missing or empty "type="
    // come before all names with non-empty type.
    //
    // When both types are missing or empty, canonical-name ordering
    // applies which is a total order.
    //
    String thisTypeKey = this.getKeyProperty("type");
    String anotherTypeKey = name.getKeyProperty("type");
    if (thisTypeKey == null) {
      thisTypeKey = "";
    }
    if (anotherTypeKey == null) {
      anotherTypeKey = "";
    }
    int typeKeyValue = thisTypeKey.compareTo(anotherTypeKey);
    if (typeKeyValue != 0) {
      return typeKeyValue;
    }

    // (3) Compare canonical names
    //
    return this.getCanonicalName().compareTo(name.getCanonicalName());
  }

  // Category : Comparable Interface <=========================

  // Public methods <========================================

}
